<pre class="metadata">
Title: CSS Route Matching
Status: ED
Work Status: Exploring
Shortname: css-navigation
Level: 1
Group: csswg
ED: https://drafts.csswg.org/css-navigation-1/
!Issue Tracking: <a href="https://github.com/w3c/csswg-drafts/issues/12594">w3c/csswg-drafts#12594</a>
Editor: L. David Baron, Google https://www.google.com/, https://dbaron.org/, w3cid 15393
Editor: Noam Rosenthal, Google https://www.google.com/, w3cid 121539
Abstract: This module contains conditional CSS rules for styling conditioned on the current URL
        or conditioned on the status of navigating between particular URLs.
</pre>

<pre class="link-defaults">
spec:css-values-5; type:function; text:if()
</pre>

<!-- FIXME: TEMPORARILY override non-exported definition -->
<pre class=anchors>
url: https://html.spec.whatwg.org/multipage/nav-history-apis.html#concept-navigationtransition-from
	type: dfn; spec: html; text: from entry;
url: https://html.spec.whatwg.org/multipage/nav-history-apis.html#window-navigation-api
	type: dfn; spec: html; text: navigation API;
url: https://html.spec.whatwg.org/multipage/nav-history-apis.html#ongoing-navigate-event
	type: dfn; spec: html; text: ongoing navigate event;
url: https://html.spec.whatwg.org/multipage/nav-history-apis.html#concept-navigation-transition
	type: dfn; spec: html; text: transition;
url: https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-activation
	type: dfn; spec: html; text: activation;
url: https://html.spec.whatwg.org/multipage/browsing-the-web.html#has-been-revealed
	type: dfn; spec: html; text: has been revealed;
url: https://drafts.csswg.org/css-view-transitions-1/#capture-the-image
	type: dfn; spec: css-view-transitions-1; text: capture the image;
</pre>

<h2 id="at-route">Declaring named URL patterns: the ''@route'' rule</h2>

The <dfn at-rule id="at-ruledef-route">@route</dfn> rule
is an at-rule that associates a name with a [=URL pattern=].
This name can be referenced in ''@navigation'' rules
and in '':link-to()'' pseudo-classes.

The syntax of the ''@route'' rule is:

<pre class="prod def" nohighlight>
@route <<dashed-ident>> {
  [ <<pattern-descriptors>> | <<init-descriptors>> ]
}
</pre>

based on the following definitions:

<pre class="prod def" dfn-type="type" nohighlight>
<dfn><<pattern-descriptors>></dfn> = ;* <<pattern-descriptor>> ;*
<dfn><<pattern-descriptor>></dfn> = pattern : <<url-pattern()>>
<dfn><<init-descriptors>></dfn> = ;* <<init-descriptor>> [ ;+ <<init-descriptor>> ]* ;*
<dfn><<init-descriptor>></dfn> = <<init-descriptor-name>> : <<string>>
<dfn><<init-descriptor-name>></dfn> = protocol | username | password | hostname | port
                         pathname | search | hash | base-url
</pre>

This associates an author-defined keyword with a URL pattern,
so that any URL that matches one of the URL patterns
matches the route named by the keyword.

The ''@route'' rule can be defined in one of two ways:

: with the <code>pattern</code> descriptor
:: in this case the URL pattern represented is
	the one represented by the <<url-pattern()>> function
	given as the descriptor's value.
: with the other descriptors named by <<init-descriptor-name>>
:: In this case the URL pattern represented is the result of invoking
	[=URL pattern/create|create a URL pattern=] given
	<var>input</var> as {{URLPatternInit}}
	constructed from the descriptors and their values.
	Each dictionary member is given the value of
	the descriptor with the same name,
	except the <code>baseURL</code> member is given the value of
	the <code>base-url</code> descriptor.
	If a <code>base-url</code> descriptor is not given then one is created from
	the [=style resource base URL=] of the rule.

ISSUE: Should this use <<dashed-ident>>, <<custom-ident>>, or <<ident>>
for the route names?

ISSUE: Is there value in being able to provide a list of <<url-pattern()>>
rather than just one?

<div class="example">
Either this rule:
<pre highlight=css>
@route --movie-list {
  pattern: url-pattern("/movie-list");
}
</pre>
or this rule:
<pre highlight=css>
@route --movie-list {
  pathname: "/movie-list";
}
</pre>
define an ''@route'' rule that associates
the name <code>--movie-list</code>
with the URL <code>"/movie-list"</code> resolved relative to the style sheet.
</div>

NOTE: The bracing syntax also allows for future expansion if needed.

NOTE: Some of the design discussion for this feature has been in
<a href="https://github.com/w3c/csswg-drafts/issues/12594">w3c/csswg-drafts#12594</a>.

<h2 id="conditional-navigation-queries">Conditional rules for navigation queries</h2>

<h3 id="at-navigation">Navigation queries: the ''@navigation'' rule</h3>

The <dfn at-rule id="at-ruledef-navigation">@navigation</dfn> rule
is a conditional group rule
whose condition tests
characteristics of the current URL
or of the state of navigation between two URLs.
These queries are called <dfn export>navigation queries</dfn>.

Authors can use it to:
* write style sheets that apply to multiple pages
	but behave somewhat differently between those pages,
* write style sheets that apply to
	single page applications
	that change their URL over time,
	so that style changes when the URL changes, and
* write style sheets that declaratively start view transitions
	(or make other appropriate style changes)
	in response to navigations.

The syntax of the condition in the ''@navigation'' rule
is similar to that defined for
<<supports-condition>> in [[CSS-CONDITIONAL-3]].
Negation, conjunction, and disjunction are all needed
so that authors can specify the interaction of multiple styles
in ways that are most intuitive and require the simplest code.

<div class="example">

The ''@navigation'' rule can be used in simple cases
to define styles that only affect a particular page:

<pre highlight="css">
@navigation (at: url-pattern("/")) {
  /* These styles only apply to the site's homepage
     (including any URL with a search or hash). */
}
</pre>

</div>

<div class="example">

The ''@navigation'' rule can also be used to define styles
that are used when a certain navigation is in progress.
This is particularly useful for defining
styles that cause [=view transitions=].

<pre highlight="css">
@route --search-results-page {
  pattern: url-pattern("/search-results");
}
@route --product-page {
  pattern: url-pattern("/product/:id");
}

@navigation ((from: --search-results-page) and
             (to: --product-page)) or
            ((from: --product-page) and
             (to: --search-results-page)) {
  /* These styles apply when a navigation is in progress
     between a search results page and a product page (as
     defined by the @route rules above), in either
     direction. */
}
</pre>

</div>

The syntax of the ''@navigation'' rule is:

<pre class="prod def" nohighlight>
@navigation <<navigation-condition>> {
	<<rule-list>>
}
</pre>

with <<navigation-condition>> defined as:

<pre class="prod def" dfn-type="type" nohighlight>
<dfn><<navigation-condition>></dfn> = not <<navigation-in-parens>>
                     | <<navigation-in-parens>> [ and <<navigation-in-parens>> ]*
                     | <<navigation-in-parens>> [ or <<navigation-in-parens>> ]*
<dfn><<navigation-in-parens>></dfn> = ( <<navigation-condition>> ) | ( <<navigation-test>> ) | <<general-enclosed>>
<dfn><<navigation-test>></dfn> = <<navigation-location>> | <<navigation-keyword>> : <<navigation-location>>
<dfn><<navigation-keyword>></dfn> = at | from | to
<dfn><<navigation-location>></dfn> = <<route-name>> | <<url-pattern()>>
<dfn><<route-name>></dfn> = <<dashed-ident>>
</pre>

The above grammar is purposely very loose for forwards-compatibility reasons,
since the <<general-enclosed>> production
allows for substantial future extensibility.
Any ''@navigation'' rule that does not parse according to the grammar above
(that is, a rule that does not match this loose grammar
which includes the <<general-enclosed>> production)
is invalid.
Style sheets <strong>must not</strong> use such a rule and
processors <strong>must</strong> ignore such a rule (including all of its contents).

Many of these grammar terms are associated with a boolean result,
as follows:

: <<navigation-condition>>
::	: not <<navigation-in-parens>>
	:: The result is the negation of the <<navigation-in-parens>> term.

	: <<navigation-in-parens>> [ and <<navigation-in-parens>> ]*
	::
		The result is true if all of the <<navigation-in-parens>> child terms are true,
		and false otherwise.

	: <<navigation-in-parens>> [ or <<navigation-in-parens>> ]*
	::
		The result is false if all of the <<navigation-in-parens>> child terms are false,
		and true otherwise.

: <<navigation-in-parens>>
:: The result is the result of the child subexpression.

: <<navigation-test>>
::	: <<navigation-location>>
	: at: <<navigation-location>>
	:: The result is whether the result of
		[=URL pattern/match|match a URL pattern=] is non-null
		given <var>urlPattern</var> as
		the [=navigation location URL pattern=] of <<navigation-location>>
		and <var>input</var> as the document's [=Document/URL=].

	: from: <<navigation-location>>
	:: The result is true if
		the [=current from URL=] <var>from</var> of the document is non-null and
		[=URL pattern/match|match a URL pattern=] is non-null when
		given <var>urlPattern</var> as
		the [=navigation location URL pattern=] of <<navigation-location>>
		and <var>input</var> as <var>from</var>.

	: to: <<navigation-location>>
	:: The result is true if
		the [=current to URL=] <var>to</var> of the document is non-null and
		[=URL pattern/match|match a URL pattern=] is non-null when
		given <var>urlPattern</var> as
		the [=navigation location URL pattern=] of <<navigation-location>>
		and <var>input</var> as <var>to</var>.

: <<general-enclosed>>
::
	The result is false.

	Authors must not use <<general-enclosed>> in their stylesheets.
	<span class='note'>It exists only for future-compatibility,
	so that new syntax additions do not invalidate too much of a <<navigation-condition>> in older user agents.</span>

The <dfn>navigation location URL pattern</dfn> of a <<navigation-location>>
depends on the type of <<navigation-location>>:

: <<route-name>>
::	the URL pattern represented by the ''@route'' rule referenced by the name.

: <<url-pattern()>>
::	The [=URL pattern=] represented by the function; see
	[=create a URL pattern for url-pattern()=].

ISSUE: Should it also be possible to reference
a name defined in a routemap?
See the
<a href="https://github.com/WICG/declarative-partial-updates/blob/main/route-matching-explainer.md">route matching explainer</a>
for details.

A <dfn>document's navigation API</dfn> is
the result of the following steps on <var>document</var>:

1. Let <var>window</var> be the {{Window}} whose [=associated Document=] is <var>document</var>, or null if there is no such {{Window}}.

1. If <var>window</var> is null, return null.

1. Return <var>window</var>'s [=navigation API=].

The condition of the ''@navigation'' rule
is the result of the <<navigation-condition>> in its prelude.

NOTE: Some of the design discussion for this feature has been in
<a href="https://github.com/w3c/csswg-drafts/issues/12594">w3c/csswg-drafts#12594</a>
and
<a href="https://github.com/w3c/csswg-drafts/issues/8209">w3c/csswg-drafts#8209</a>.

<h3 id="navigation-when-function">The ''@when/navigation()'' function for ''@when''</h3>

This specification defines an additional function for the ''@when'' rule:

<pre class="prod">
<dfn for="@when" function>navigation()</dfn> = navigation( <<navigation-condition>> )
</pre>

The ''@when/navigation()'' function is associated with the boolean result that
its contained condition is associated with.

<h3 id="navigation-if-function">The ''if()/navigation()'' function for ''if()''</h3>

This specification defines an additional function for the ''if()'' function's
<<if-test>> production:

<pre class="prod">
<dfn for="if()" function>navigation()</dfn> = navigation( <<navigation-condition>> )
</pre>

ISSUE: This should probably have a more formal definition of the function,
but I can't find the formal definitions of the existing ''if()'' functions
to model it after.

<h2 id="link-navigation-pseudo-classes">Pseudo-class for navigation-related links: '':link-to()''</h2>

This specification defines a new
<dfn id="link-to-pseudo" selector>'':link-to()''</dfn> functional pseudo-class
that matches link elements that link to a certain URL.

<div class="example">

A simple example of a ''::link-to()'' selector is this one,
which matches any links that link to the site's homepage:

<pre highlight=css>
:link-to(url-pattern("/")) {
  font-weight: bold;
}
</pre>

</div>

<div class="example">

A more interesting example of the ''::link-to()'' pseudo-class
is this example which creates a view transition between
a item in a list that contains a link (in this document)
and the details page for that link (in a different document).
This transition works even when the navigation is a back/forward navigation
and even if the user has used a language selector UI
to change the page into a different language (and thus change the URL).
The use of the '':link-to()'' pseudo-class ensures that
the view transition animations from or to the correct item in the list
by matching the relevant parts of the navigation URL to the link URL.

<pre highlight=css>
@view-transition {
  /* allow cross-document view transitions */
  navigation: auto;
}

@route --movie-details {
  /* match URLs like /en/movie/123 which is the English page
     about a movie with ID 123 */
  pattern: url-pattern("/:lang/movie/:id");
}

/* capture the overall area representing the movie, and a
   sub-area for its poster image */

/* match an element with class movie-container with a child
   link that links to a movie whose id is the same as the
   movie we are currently navigating to or from.  (lang can
   be different, though.)

   Just :link-to(--movie-details) requires that the target
   of the link match the URL pattern defined by the "@route
   --movie-details" rule.

   The navigation-param(id) further requires that either the
   from or the to URL of the current navigation also match
   the URL pattern represented by the "@route
   --movie-details" rule, and that that the 'id' named group
   from that match be the same as the 'id' named group from
   the match with the link's target.
   */
.movie-container:has(> .movie-title:link-to(
  --movie-details with navigation-param(id))) {

  view-transition-name: movie-container;

  > .movie-poster {
    view-transition-name: movie-poster;
  }

  /* leave the default cross-fade animation for both image
     captures */
}
</pre>

</div>

The '':link-to()'' pseudo-class takes a single argument, a <<link-condition>>,
and the pseudo-class matches any element where:
* the element matches '':any-link''
* the target of link matches the <<link-condition>>, as defined below.

<pre class="prod def" dfn-type="type" nohighlight>
<dfn><<link-condition>></dfn> = <<link-condition-base>> [ with <<navigation-param-expression>> ]?
<dfn><<link-condition-base>></dfn> = <<navigation-location>>
<dfn><<navigation-param-expression>></dfn> = ( <<navigation-param-and>> ) |
                                ( <<navigation-param-or>> ) |
                                <<navigation-param>>
<dfn><<navigation-param-and>></dfn> = <<navigation-param-expression>>
                         [ "and" <<navigation-param-expression>> ]*
<dfn><<navigation-param-or>></dfn> = <<navigation-param-expression>>
                        [ "or" <<navigation-param-expression>> ]*
<dfn><<navigation-param>></dfn> = <<navigation-param-key-value>> | <<navigation-param-function>>
<dfn><<navigation-param-key-value>></dfn> = ( <<ident>> : <<string>> )
<dfn><<navigation-param-function>></dfn> = navigation-param( <<ident>> )
</pre>

A <<link-condition>> matches the target of the link when both:
* the <<link-condition-base>> matches the target of the link, and
* the <<navigation-param-expression>> matches the target of the link,
	with the [=URL pattern=] represented by the <<link-condition-base>> as context

A <<link-condition-base>> represents a [=URL pattern=].
If the <<link-condition-base>> is a <<url-pattern()>>,
then it represents the URL pattern
represented by the <<url-pattern()>> function
(see [=create a URL pattern for url-pattern()=]).
If it is a <<route-name>>, then it represents the URL pattern
represented by the ''@route'' rule.

A <<link-condition-base>> matches a URL
when [=URL pattern/match|match a URL pattern=] is non-null
given <var>urlPattern</var> as
the [=URL pattern=] that it represents and
<var>input</var> as the given URL.

A <<navigation-param-expression>> matches a URL
(with a [=URL Pattern=] as context)
based on standard boolean logic for <code>and</code> and <code>or</code>,
and based on whether each <<navigation-param>> matches the URL
(with the URL Pattern as context).

A <<navigation-param>> matches the URL <var>input</var> (with a URL Pattern <var>urlPattern</var> as context)
if the following steps return true:
1. Let <var>matchResult</var> be the result of
	[=URL pattern/match|match a URL pattern=]
	given <var>urlPattern</var> and <var>input</var>.
	the [=URL pattern=]
	represented by the <<url-pattern()>> function in the <<link-condition-base>>
	(see [=create a URL pattern for url-pattern()=])
1. If <var>matchResult</var> is null, return false.

	NOTE: This doesn't really matter because
	in this case the <<link-condition-base>> also doesn't match.
1. For each property <var>prop</var> of <var>matchResult</var> (a {{URLPatternResult}})
	that is a {{URLPatternComponentResult}}:
	1. For each [=map/entry=] <var>entry</var> in its {{URLPatternComponentResult/groups}},
		then:
		1. If <<navigation-param>> is a <<navigation-param-key-value>>,
			return true if
			<var>entry</var>'s [=map/key=] is the <<ident>>
			and <var>entry</var>'s [=map/value=] is the <<string>>.
		1. If <<navigation-param>> is a <<navigation-param-function>>,
			and <var>entry</var>'s [=map/key=] is the function's <<ident>>,
			then for each <var>navigationUrl</var> of the
			[=current to URL=]
			and [=current from URL=]:
			1. Let <var>navigationMatchResult</var> be the result of
				[=URL pattern/match|match a URL pattern=]
				given <var>urlPattern</var> and <var>navigationUrl</var>.
			1. If <var>navigationMatchResult</var> is null, [=continue=].
			1. Return true if <var>navigationMatchResult</var>'s
				property <var>prop</var>
				has an [=map/entry=]
				whose [=map/key=] is the same as <var>entry</var>'s key
				and whose [=map/value=] is the same as <var>entry</var>'s value.

				NOTE: This step makes the ''navigation-param()'' function
				perform what is essentially a three-way match,
				between the target of the link,
				the provided URL pattern,
				and the from or to URL of the current navigation.

NOTE: Some of the design discussion for this feature has been in
<a href="https://github.com/w3c/csswg-drafts/issues/13163">w3c/csswg-drafts#13163</a>.

<h2 id="current-nav-urls">Current navigation URLs</h2>

Both the ''@navigation'' rule and the '':link-to()'' pseudo-class
rely on the following definitions of
the [=current from URL=] and [=current to URL=].

The <dfn>current from URL</dfn> of a [=/document=] is a URL or null.
It is defined as follows:

1. If the [=document's navigation API=] of the document is non-null and
	its [=transition=] is non-null,
	its [=from entry=]'s {{NavigationHistoryEntry/url}}.

	NOTE: This part is for when the old document in the navigation
	is still the current document.

1. If the [=document's navigation API=] of the document is non-null and
	its [=activation=] is non-null,
	the document's [=has been revealed=] is false or
	was false at the start of the current [=task=],
	the activation's {{NavigationActivation/from}}'s
	{{NavigationHistoryEntry/url}}.

	NOTE: This part is for when the new document in the navigation
	has become the current document.

1. Otherwise, null.

	NOTE: The previous two branches can also produce null results.

The <dfn>current to URL</dfn> of a [=/document=] is a URL or null.
It is defined as follows:

1. If the [=document's navigation API=] of the document is non-null and
	its [=ongoing navigate event=] is non-null:

	1. if the {{pageswap}} event has fired since that navigation began,
		and its {{PageSwapEvent/activation}} was non-null,
		and that {{PageSwapEvent/activation}}'s
		{{NavigationActivation/entry}}'s
		{{NavigationHistoryEntry/url}} is non-null,
		then that
		{{NavigationHistoryEntry/url}}.

		NOTE: This part <em>does</em> expose the result of redirects.

		NOTE: This part is not relevant to normal page rendering.
		However, it can be relevant to what is rendered
		when [=capturing the image=]
		for a [[css-view-transitions-2#cross-document-view-transitions|cross-document view transition]].

		ISSUE: Is the final "non-null" check needed?

	1. otherwise, the [=ongoing navigate event=]'s
		{{NavigateEvent/destination}}'s
		{{NavigationDestination/url}}

		NOTE: This part does <em>not</em> expose the result of redirects.

	ISSUE: This assumes that the [=ongoing navigate event=]
	and the [=transition=] have the same lifetime,
	but this isn't really
	true if the event is intercepted.
	After
	<a href="https://github.com/whatwg/html/issues/11690">whatwg/html#11690</a> /
	<a href="https://github.com/whatwg/html/pull/11692">whatwg/html#11692</a>.
	we could probably define this more like "from" above.
	But which lifetime is the one we want?

	NOTE: This part is for when the old document in the navigation
	is still the current document.

1. If the [=document's navigation API=] of the document is non-null and
	its [=activation=] is non-null,
	the document's [=has been revealed=] is false or
	was false at the start of the current [=task=],
	and the activation's {{NavigationActivation/entry}}'s
	{{NavigationHistoryEntry/url}}.

	NOTE: This part is for when the new document in the navigation
	has become the current document.

1. Otherwise, null.

	NOTE: The previous two branches can also produce null results.

ISSUE: The above definitions of from and to apparently don't work right
if you start a same-document navigation (e.g., with {{History/pushState}})
in the middle of a cross-document navigation.

ISSUE: Generally improve integration with the HTML spec for these definitions,
instead of monkeypatching.
This includes the interaction with [=has been revealed=]
and the interaction with the {{pageswap}} event,
and other things where this section links to non-exported definitions.

<h2 id="url-pattern-function">The ''url-pattern()'' function</h2>

<!--

NOTE: We may eventually want to move this to css-values.

If we do, the definition of "style resource base URL" probably doesn't need to be
exported any more, since it was exported for this definition.

-->

The <dfn export function>url-pattern()</dfn> function represents a [=URL pattern=],
which can be used to match URLs.

<pre class="prod def">
<<url-pattern()>> = url-pattern( <<string>> )
</pre>

This function represents a [=URL pattern=] that can be created
using the steps of the <dfn>create a URL pattern for url-pattern()</dfn> algorithm:

1. Let <var>arg</var> be the <<string>> argument to the ''url-pattern()'' function.

1. Let <var>baseURL</var> be the [=style resource base URL=] of
	the rule or declaration block containing the ''url-pattern()'' function.

	<div class="issue">
	Do we want this to be the base URL all the time?
	For use of ''url-pattern()'' in ''@navigation'',
	it's likely more useful for the base URL
	to be the document URL rather than the style sheet URL.
	However, it would be very awkward for ''url-pattern()''
	to be inconsistent with ''url()''.

	Should we allow the base URL of ''url-pattern()''
	to be defined by the consumer?
	Should we introduce <code>document-url-pattern()</code>?
	Should we do something similar to
	[[css-images-3#ambiguous-urls]]
	(see <w3c/csswg-drafts#383>)?

	Also see other proposed uses of {{URLPattern}} in CSS
	in <w3c/csswg-drafts#10975>,
	for '':local-link''.
	</div>

1. Return the result of [=URL pattern/create|create a URL pattern=] given
	<var>arg</var>, <var>baseURL</var>, and an empty [=map=].

NOTE: This function requires that its argument is quoted.
This differs from the ''url()'' function,
which allows its argument to be quoted or unquoted.

To <dfn export>serialize a ''url-pattern()'' function</dfn> <var>f</var>,
[=serialize a function=] <var>f</var>,
using [=serialize a string=] on the single argument
to serialize <var>f</var>'s contents.

NOTE: This is defined this way because {{URLPattern}}
intentionally does not provide a serialization.
